package com.dimples.framework.aspect;

import com.alibaba.fastjson.JSON;
import com.dimples.common.annotation.OpsLog;
import com.dimples.common.aop.BaseAspectSupport;
import com.dimples.common.enums.LoggingTypeEnum;
import com.dimples.common.enums.OpsLogTypeEnum;
import com.dimples.common.enums.StatusEnum;
import com.dimples.common.exception.BizException;
import com.dimples.common.manager.AsyncManager;
import com.dimples.common.service.DimplesJwtTokenServiceImpl;
import com.dimples.common.util.HttpContextUtil;
import com.dimples.common.util.SpringContextHolder;
import com.dimples.system.entity.Logging;
import com.dimples.system.service.LoggingService;

import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.HandlerMapping;

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Date;
import java.util.Enumeration;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import cn.hutool.core.date.DateUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * 日志记录切面类
 *
 * @author zhongyj <1126834403@qq.com><br/>
 * @date 2019/11/8
 */
@Slf4j
@Component
@Aspect
public class OpsLogAspect extends BaseAspectSupport {

    private static final String LOG_CONTENT = "[ 类名 ] ==> %s \n[ 方法 ] ==> %s \n[ 参数 ] ==> %s \n[ I P ] ==> %s \n[ 时间 ] ==> %s ";

    @Pointcut(value = "@annotation(com.dimples.common.annotation.OpsLog)")
    public void opsLogAnnotation() {
    }

    @Around("opsLogAnnotation()")
    public Object recordSysLog(ProceedingJoinPoint point) throws Throwable {
        long start = System.currentTimeMillis();
        // 先执行业务
        Object result = point.proceed();
        try {
            handleLog(point, start);
        } catch (Exception e) {
            throw new BizException("日志记录出错", e);
        }
        return result;
    }

    /**
     * 创建日志记录
     * 1、写入到数据库
     * 2、写入到文件中
     *
     * @param point ProceedingJoinPoint
     * @param start 方法开始时间
     */
    private void handleLog(ProceedingJoinPoint point, long start) {
        Method currentMethod = resolveMethod(point);
        String className = point.getTarget().getClass().getName();
        String methodName = currentMethod.getName();
        // 获取注解中的内容
        OpsLog opsLog = currentMethod.getAnnotation(OpsLog.class);
        String value = opsLog.value();
        OpsLogTypeEnum[] opsLogTypeEnums = opsLog.type();
        String opsLogTypeName = null;
        if (opsLogTypeEnums.length > 0) {
            opsLogTypeName = opsLogTypeEnums[0].name();
        }
        // 获取请求的IP
        HttpServletRequest request = HttpContextUtil.getRequest();
        // 构建一些基础信息
        String content = buildContent(point, methodName, request);
        log.info("\n =============================== {} ========================================\n" +
                        "[ 用时 ] ==> {} 毫秒 \n" +
                        "[ 类型 ] ==> {} \n" +
                        "{} \n" +
                        "======================================================================================",
                value, System.currentTimeMillis() - start, opsLogTypeName, content);
        Logging logging = new Logging();
        logging.setTitle(value);
        logging.setBusinessType(opsLogTypeName);
        logging.setMethod(request.getMethod());
        logging.setRequestMethod(className + "." + methodName + "()");
        logging.setRequestParam(getParams(request, point.getArgs()).toString());
        logging.setOperateName(SpringContextHolder.getBean(DimplesJwtTokenServiceImpl.class).getUsernameFromToken(request));
        logging.setOperateUrl(request.getRequestURI());
        logging.setOperateIp(HttpContextUtil.getIp());
        setRequestValue(point, logging);
        logging.setSuccess(StatusEnum.YES.getCode());
        logging.setBrowser(HttpContextUtil.getBrowserName(request) + " " + HttpContextUtil.getBrowserVersion(request));
        logging.setCreateTime(new Date());
        logging.setSystemOs(HttpContextUtil.getOsName(request));
        logging.setLoggingType(LoggingTypeEnum.OPERATE.name());
        AsyncManager.me().execute(SpringContextHolder.getBean(LoggingService.class).saveOperaLog(logging));
    }

    /**
     * 获取方法，请求等信息
     *
     * @param joinPoint  ProceedingJoinPoint
     * @param methodName String
     * @param request    HttpServletRequest
     * @return String
     */
    private String buildContent(ProceedingJoinPoint joinPoint, String methodName, HttpServletRequest request) {
        String className = joinPoint.getTarget().getClass().getName();
        Object[] params = joinPoint.getArgs();
        StringBuilder bf = getParams(request, params);
        return String.format(LOG_CONTENT, className, methodName, bf, HttpContextUtil.getIp(), DateUtil.now());
    }

    private StringBuilder getParams(HttpServletRequest request, Object[] params) {
        StringBuilder bf = new StringBuilder();
        if (params != null && params.length > 0) {
            Enumeration<String> paraNames = request.getParameterNames();
            while (paraNames.hasMoreElements()) {
                String key = paraNames.nextElement();
                bf.append(key).append("=").append(request.getParameter(key)).append("&");
            }
            if (bf.toString().isEmpty()) {
                bf.append(request.getQueryString());
            } else {
                bf.deleteCharAt(bf.length() - 1);
            }
        }
        return bf;
    }

    /**
     * 获取请求的参数，放到log中
     *
     * @param logging 操作日志
     */
    private void setRequestValue(JoinPoint joinPoint, Logging logging) {
        String requestMethod = logging.getRequestMethod();
        if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)) {
            String params = argsArrayToString(joinPoint.getArgs());
            logging.setRequestBody(StringUtils.substring(params, 0, 2000));
        } else {
            Map<?, ?> paramsMap = (Map<?, ?>) HttpContextUtil.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
            logging.setRequestBody(StringUtils.substring(paramsMap.toString(), 0, 2000));
        }
    }

    /**
     * 参数拼装
     */
    private String argsArrayToString(Object[] paramsArray) {
        StringBuilder params = new StringBuilder();
        if (paramsArray != null && paramsArray.length > 0) {
            for (Object o : paramsArray) {
                if (!isFilterObject(o)) {
                    Object jsonObj = JSON.toJSON(o);
                    params.append(jsonObj.toString()).append(" ");
                }
            }
        }
        return params.toString().trim();
    }

    /**
     * 判断是否需要过滤的对象。
     *
     * @param o 对象信息。
     * @return 如果是需要过滤的对象，则返回true；否则返回false。
     */
    @SuppressWarnings("rawtypes")
    public boolean isFilterObject(final Object o) {
        Class<?> clazz = o.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) o;
            for (Object value : collection) {
                return value instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) o;
            for (Object value : map.entrySet()) {
                Map.Entry entry = (Map.Entry) value;
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse;
    }
}


















